/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/
package com.genia.toolbox.basics.editor;

import java.io.File;
import java.rmi.server.UID;
import java.util.List;
import java.util.Locale;

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.UrlResource;

import com.genia.toolbox.basics.editor.exception.InitialisationException;
import com.genia.toolbox.basics.editor.message.AbstractMessages;
import com.genia.toolbox.basics.editor.model.bean.InitialSettings;
import com.genia.toolbox.basics.editor.model.document.Document;
import com.genia.toolbox.basics.editor.model.document.Element;
import com.genia.toolbox.basics.exception.BundledException;
import com.genia.toolbox.basics.exception.technical.TechnicalIOException;
import com.genia.toolbox.basics.exception.technical.XmlException;
import com.genia.toolbox.basics.i18n.I18nMessage;
import com.genia.toolbox.basics.manager.XmlManager;
import com.genia.toolbox.spring.initializer.ApplicationContextHolder;
import com.genia.toolbox.spring.initializer.ProcessInitializer;
import com.genia.toolbox.spring.provider.message.manager.CustomResourceBundleMessageSource;
import com.genia.toolbox.spring.provider.plugin.impl.BasicSpringProviderPlugin;
import com.genia.toolbox.web.portlet.description.ContainerPortletDescription.LayoutDirection;

/**
 * The basic application.
 * 
 * @param <C>
 *          The generic controller.
 * @param <D>
 *          The generic document.
 * @param <E>
 *          The generic element.
 * @param <I>
 *          The generic initial settings.
 */
public abstract class AbstractApplication<C extends AbstractController<?, ?, D, E, I>, D extends Document<E>, E extends Element<?>, I extends InitialSettings>
{

  /**
   * The controller.
   */
  private C controller;

  /**
   * The application context.
   */
  private ApplicationContext context = null;

  /**
   * The ressource bundle.
   */
  private CustomResourceBundleMessageSource messageSource = null;

  /**
   * The current locale.
   */
  private Locale locale = null;

  /**
   * The cut element.
   */
  private E cutElement = null;



  /**
   * Constructor.
   */
  public AbstractApplication()
  {
    super();
  }



  /**
   * Initialise the application.
   * 
   * @param displayGUI
   *          Whether the GUI is displayed.
   */
  public void launch(boolean displayGUI)
  {
    try {

      // Initialise the data.
      this.context = ApplicationContextHolder.getApplicationContext();
      this.messageSource = (CustomResourceBundleMessageSource) this.context.getBean("messageSource");
      this.locale = Locale.getDefault();

      this.doInitialise();

      // Display the gui.
      if (displayGUI) {
        this.controller.getGUI().setVisible(true);
      }
    }
    catch (BundledException e) {
      e.printStackTrace();
      this.controller.notifyInitialisationError("FATAL ERROR", e.getLocalizedMessage());
    }
    catch (Exception e) {
      e.printStackTrace();
      InitialisationException exception = new InitialisationException();
      this.controller.notifyInitialisationError("FATAL ERROR", exception.getLocalizedMessage());
    }

  }



  /**
   * Perform tasks while initialising.
   * 
   * @exception BundledException
   *              If there is an initialisation error.
   */
  public abstract void doInitialise()
      throws BundledException;



  /**
   * Get the application context.
   * 
   * @return the application context.
   */
  public ApplicationContext getApplicationContext()
  {
    return this.context;
  }



  /**
   * Get the translation.
   * 
   * @param key
   *          the key.
   * @return the translation.
   */
  public String getTranslation(String key)
  {
    return this.messageSource.getMessage(key, null, this.locale);
  }



  /**
   * Get the translation.
   * 
   * @param key
   *          the key.
   * @param parameters
   *          the parameters.
   * @return the translation.
   */
  public String getTranslation(String key, String... parameters)
  {
    return this.messageSource.getMessage(key, parameters, this.locale);
  }



  /**
   * Get the translation.
   * 
   * @param message
   *          the message.
   * @return the translation.
   */
  public String getTranslation(I18nMessage message)
  {
    return this.messageSource.getMessage(message, this.locale);
  }



  /**
   * Get the controller.
   * 
   * @return the controller.
   */
  public C getController()
  {
    return this.controller;
  }



  /**
   * set the controller.
   * 
   * @param controller
   *          the controller to set
   */
  public void setController(C controller)
  {
    this.controller = controller;
  }



  /**
   * Close the application.
   */
  public void close()
  {
    System.exit(0);
  }



  /**
   * Open a new document.
   */
  public void openDocument()
  {
    File xml = this.getController().notifyOpenAFile();
    if (xml != null) {
      D document = this.loadDocument(xml);
      if (document != null) {
        this.getController().notifyFirstDisplayDocument(document);
      }
    }
  }



  /**
   * Load a document from its xml file.
   * 
   * @param xml
   *          the xml file.
   * @return the loaded document.
   */
  public abstract D loadDocument(File xml);



  /**
   * Create a new document.
   * 
   * @param settings
   *          The document initial settings.
   */
  public abstract void createNewDocument(I settings);



  /**
   * Close the document.
   * 
   * @param document
   *          The document.
   */
  public void closeDocument(D document)
  {
    if (document != null) {
      if (!document.isSaved()) {
        boolean answer = this.getController().notifyAskUser(this.getTranslation(AbstractMessages.BASIC_DOCUMENT_ERROR_CLOSING_TITLE),
            this.getTranslation(AbstractMessages.BASIC_DOCUMENT_ERROR_CLOSING_MESSAGE, document.getName()));
        if (answer) {
          this.saveDocument(document);
        }
      }
      this.getController().notifyHideDocument(document.getUniqueId());
    }
  }



  /**
   * Save the document.
   * 
   * @param document
   *          the document.
   */
  public abstract void saveDocument(D document);



  /**
   * Save all the documents.
   */
  public void saveAllDocuments()
  {
    List<UID> documentsUID = this.getController().notifyListDisplayedDocuments();
    for (UID uid : documentsUID) {
      this.saveDocument(this.getController().notifyRefreshDocument(uid));
    }
  }



  /**
   * Close all the documents.
   */
  public void closeAllDocuments()
  {
    List<UID> documentsUID = this.getController().notifyListDisplayedDocuments();
    for (UID uid : documentsUID) {
      this.closeDocument(this.getController().notifyRefreshDocument(uid));
    }
  }



  /**
   * Create a new sub-element.
   * 
   * @param settings
   *          The document initial settings.
   * @param documentUniqueID
   *          the document unique ID.
   * @param parentUniqueID
   *          the parent unique ID.
   * @param direction
   *          the parent direction.
   */
  public abstract void createNewSubElement(I settings, UID documentUniqueID, UID parentUniqueID, LayoutDirection direction);



  /**
   * Update a document which has its settings modified.
   * 
   * @param document
   *          the updated document.
   */
  public void updateDocumentFromSettings(D document)
  {
    E updatedElement = this.getController().notifyRefreshElementFromSettings();
    if (updatedElement != null && document.getUniqueId().equals(updatedElement.getDocumentID())) {
      document.updateElement(updatedElement);
    }
  }



  /**
   * Trim the content of an XML file.
   * 
   * @param xml
   *          The XML file.
   * @throws TechnicalIOException
   *           If the trim failed.
   * @throws XmlException
   *           If the trim failed.
   */
  protected void trimFileContent(File xml)
      throws XmlException, TechnicalIOException
  {
    XmlManager xmlManager = (XmlManager) new XmlBeanFactory(new UrlResource(ProcessInitializer.class.getResource(BasicSpringProviderPlugin.XML_MANAGER_CONFIG_FILE_RESOURCE))).getBean("xmlManager");
    org.w3c.dom.Document xmlDocument = xmlManager.parse(xml);
    xmlManager.stripWhiteSpaceNodes(xmlDocument.getFirstChild());
    xmlManager.print(xmlDocument, xml);
  }



  /**
   * Get the cut element.
   * 
   * @return the cut element.
   */
  public E getCutElement()
  {
    return this.cutElement;
  }



  /**
   * Set the cut element.
   * 
   * @param element
   *          the cut element.
   */
  public void setCutElement(E element)
  {
    this.cutElement = element;
  }



  /**
   * Paste an element.
   * 
   * @param element
   *          The element.
   * @param parent
   *          the parent element.
   */
  public void pasteElement(E element, E parent)
  {
    if (element != null && parent != null) {
      element.setDocumentID(parent.getDocumentID());
      element.setParentID(parent.getUniqueID());
      this.getController().notifyDisplaySubElement(element);
    }
  }

}
